package ss.pku.jsa.type.inference;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Name;

@SuppressWarnings({ "unchecked", "rawtypes" })
public class CHAGraph implements Serializable {

	private static final long serialVersionUID = 1L;
	public Map<MethodKey, Set<MethodKey>> g = new HashMap();
	// private Set<IJavaProject> projects;
	private Set<ICompilationUnit> compilationUnits = new HashSet();
	private Set<String> missingMethods = new HashSet();

	public CHAGraph(Set<IJavaProject> projects) throws JavaModelException {
		// this.projects = projects;
		for (IJavaProject p : projects) {
			for (IPackageFragment pk : p.getPackageFragments()) {
				for (ICompilationUnit c : pk.getCompilationUnits()) {
					this.compilationUnits.add(c);
				}
			}
		}
		run();
	}

	public static Map<MethodKey, Set<MethodKey>> getJavalibCHAGraph()
			throws FileNotFoundException, IOException, ClassNotFoundException {
		ObjectInputStream in = new ObjectInputStream(new FileInputStream(
				"JavalibCHAGraph"));
		Map g = (Map) in.readObject();
		return g;
	}

	public void addAll(Map<MethodKey, Set<MethodKey>> g) {
		for (Map.Entry e : g.entrySet()) {
			MethodKey m = (MethodKey) e.getKey();
			Set<MethodKey> s = (Set) e.getValue();
			for (MethodKey n : s)
				addEdge(m, n);
		}
	}

	public boolean overrides(MethodKey subM, MethodKey superM) {
		Set overridden = getOverriddenSet(subM);
		if (overridden == null)
			return false;
		return overridden.contains(superM);
	}

	public Set<MethodKey> getOverriddenSet(MethodKey subM) {
		return (Set) this.g.get(subM);
	}

	private void run() {
		ASTParser parser = ASTParser.newParser(3);
		for (ICompilationUnit t : this.compilationUnits) {
			try {
				parser.setSource(t);
				parser.setResolveBindings(true);
				CompilationUnit compilationUnit = (CompilationUnit) parser
						.createAST(null);
				t.getUnderlyingResource().getName();
				Visitor visitor = new Visitor();
				compilationUnit.accept(visitor);
			} catch (JavaModelException e) {
				throw new RuntimeException("JME: " + e);
			}
		}
		JqualPlugin.printlnLog("missingMethodsCHALog",
				this.missingMethods.toString());
	}

	public boolean addEdge(MethodKey subM, MethodKey superM) {
		Set s = (Set) this.g.get(subM);
		if (s == null) {
			s = new HashSet();
			this.g.put(subM, s);
		}
		return s.add(superM);
	}

	public Set<MethodKey> reachableFromSet(Set<MethodKey> s) {
		Set ret = new HashSet(s);
		Set workset = new HashSet(s);
		while (!workset.isEmpty()) {
			MethodKey caller = (MethodKey) workset.iterator().next();
			workset.remove(caller);
			Set<MethodKey> callees = (Set) this.g.get(caller);
			if (callees != null) {
				for (MethodKey callee : callees) {
					if (!ret.contains(callee)) {
						workset.add(callee);
						ret.add(callee);
					}
				}
			}
		}
		return ret;
	}

	public String toString() {
		StringBuilder ret = new StringBuilder();
		for (MethodKey subM : this.g.keySet()) {
			ret.append(subM + " overrides ");
			for (MethodKey superM : (Set<MethodKey>) this.g.get(subM))
				ret.append(superM + " ");
			ret.append("\n");
		}
		return ret.toString();
	}

	class Visitor extends ASTVisitor {
		Visitor() {
		}

		public boolean visit(MethodDeclaration node) {
			IMethodBinding m = node.resolveBinding();
			if (m == null) {
				Name n = node.getName();
				String name;
				if (n != null)
					name = n.toString();
				else
					name = node.toString();
				CHAGraph.this.missingMethods.add(name);
			} else {
				findOverridden(m);
			}
			return true;
		}

		private void findOverridden(IMethodBinding m) {
			ITypeBinding t = m.getDeclaringClass();
			ITypeBinding st = t.getSuperclass();
			Profile.logMemStart("findOverridden/new MethodKey (1)");
			MethodKey k = MethodKey.getMethodKey(m);
			Profile.logMemEnd();
			while (st != null) {
				for (IMethodBinding sm : st.getDeclaredMethods()) {
					if (m.overrides(sm)) {
						Profile.logMemStart("findOverridden/new MethodKey (2)");
						MethodKey k2 = MethodKey.getMethodKey(sm);
						Profile.logMemEnd();
						CHAGraph.this.addEdge(k, k2);
						break;
					}
				}
				st = st.getSuperclass();
			}
		}
	}
}
